﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace Organized
{
    // Post processor that applies a Gaussian blur to an image or the frame buffer
    public class NiceRendering : PostProcessor
    {

  
        //visual settings
        public float dofStart = 350f;
        public float dofLength = 2400f;
        public float dofDensity = 1f;



        //bloom settings
        public float BloomIntensity = 0.64f;
        public float BloomSaturation = 0.39f;

        public float BaseIntensity = 0.57f;
        public float BaseSaturation = 0.57f;

        public float BloomThreshold = 0.63f;
        //Scatter sunlight exposure
        public float Exposition = 0.22f;
        
        
        

        

        //The camera object to get view matrixes
        Camera camera;

        //The effect file for blurring an image
        Effect blurEffect;
        Effect DofEffect;
        Effect ScatterEffect;
        Effect bloomExtractEffect;
        Effect bloomCombineEffect;

        //postprocessing rendertargets
        RenderTarget2D renderTarget1;
        RenderTarget2D renderTarget2;
        RenderTarget2D renderTarget5;
        RenderTarget2D renderTarget6;    
        RenderTarget2D renderTarget8;
      

        Texture2D depthBuffer;
        Texture2D blurBuffer;
        Texture2D colorBuffer;
        Texture2D processBuffer;
        Texture2D bloomBuffer;

        Vector2 texelSize;

        // Offsets and wieghts for horizontal and vertical blurs
        Vector2[] sampleOffsetsH = new Vector2[15];
        float[] sampleWeightsH = new float[15];

        Vector2[] sampleOffsetsV = new Vector2[15];
        float[] sampleWeightsV = new float[15];

        // Offsets and wieghts for horizontal and vertical blurs
        Vector2[] sampleOffsetsHBloom = new Vector2[15];
        float[] sampleWeightsHBloom = new float[15];

        Vector2[] sampleOffsetsVBloom = new Vector2[15];
        float[] sampleWeightsVBloom = new float[15];

        // Direction of Gaussian Blur: Horizontal or Vertical
        public enum GaussianBlurDirection { Horizontal, Vertical };

        public NiceRendering(Camera _camera, int Width, int Height, GameScreen Parent)
            : base(Engine.Content.Load<Effect>("Content/Effects/DepthOfFieldEffect"), Width, Height, Parent) 
        {

         
            //load the blur effect file
            blurEffect = Engine.Content.Load<Effect>("Content/Effects/BlurEffect");
            DofEffect = this.Effect;
            ScatterEffect = Engine.Content.Load<Effect>("Content/Effects/ScatterEffect");

            bloomExtractEffect = Engine.Content.Load<Effect>("Content/Effects/BloomExtract");
            bloomCombineEffect = Engine.Content.Load<Effect>("Content/Effects/BloomCombine");

            camera = _camera;
            PresentationParameters pp = Engine.GraphicsDevice.PresentationParameters;
            //DepthMap RenderTarget
            renderTarget1 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, 1, pp.BackBufferFormat);
            //rt 2+3 is used to print blurred images on, these are half sized rendertargets
            renderTarget2 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth / 2, pp.BackBufferHeight / 2, 1, pp.BackBufferFormat);
            //renderTarget3 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth / 2, pp.BackBufferHeight / 2, 0, pp.BackBufferFormat);
            //rt 6 + 7 are used to print blurred BLoom images on
            renderTarget6 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth / 4, pp.BackBufferHeight / 4, 1, pp.BackBufferFormat);
             //particle rendertarget
            //renderTarget4 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth , pp.BackBufferHeight, 1, pp.BackBufferFormat);
            //bloom extract rendertarget
            renderTarget5 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth/2, pp.BackBufferHeight/2, 1, pp.BackBufferFormat);


            renderTarget8 = new RenderTarget2D(Engine.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, 1, pp.BackBufferFormat);
           
            // Set up the sampling parameters for the blur portion of the effect
            //Depth of field Blur settings
            texelSize.X = 1f / Width;
            texelSize.Y = 1f / Height;
            SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH);
            SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV);    
            //Bloom Blur settings
            texelSize.X = 5f / Width;
            texelSize.Y = 5f / Height;
            SetBlurParameters(texelSize.X, 0, ref sampleOffsetsHBloom, ref sampleWeightsHBloom);
            SetBlurParameters(0, texelSize.Y, ref sampleOffsetsVBloom, ref sampleWeightsVBloom);
        }


        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
        {
            // The first sample always has a zero offset.
            fSampleWeights[0] = ComputeGaussian(0);
            vSampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = fSampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < 15 / 2; i++)
            {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);

                fSampleWeights[i * 2 + 1] = weight;
                fSampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                vSampleOffsets[i * 2 + 1] = delta;
                vSampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < fSampleWeights.Length; i++)
                fSampleWeights[i] /= totalWeights;
        }

        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        private float ComputeGaussian(float n)
        {
            float theta = 2.0f + float.Epsilon;
            return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
        }

        // Set blur parameters to effect
        void SetParametersBlur(GaussianBlurDirection Direction)
        {
            if (Direction == GaussianBlurDirection.Horizontal)
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
            }
            else
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);
            }
        }

        void SetParametersBloom(GaussianBlurDirection Direction)
        {
            if (Direction == GaussianBlurDirection.Horizontal)
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsHBloom);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsHBloom);
            }
            else
            {
                blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsVBloom);
                blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsVBloom);
            }
        }       

       
        public override void Draw()
        {
            //try this:
            //1) get last buffer as colorbuffer
            //2) apply light scattering to the colorbuffer, save this as scatterbuffer
            //3) create a bloomactract from this one and blur it            
            //5) create a bloomed scene by combining the bloom extract with the scatterbuffer
            //6) store the bloomed scene as new colorbuffer
            //7) create depth map
            //8) create blurbuffer from bloomed scene
            //9) create depth of field from blurbuffer, bloomed scene and depthmap
            

            
            
            //1)//get colorbuffer
            GetInputFromFrameBuffer();
            //processBuffer = Input;


           
            //2)//////////////////////////////////////////////////////////////////////
            //LIGHT SCATTERING
           
            ShaderEnabled = true;
            //GetInputFromFrameBuffer();
            
            //colorBuffer = Input;
            //scatter settings
          
            ScatterEffect.CurrentTechnique = ScatterEffect.Techniques["Scatter"];
            this.Effect = ScatterEffect;
            ScatterEffect.Parameters["frameTex"].SetValue(Input);
            ScatterEffect.Parameters["Exposition"].SetValue(this.Exposition);
            ScatterEffect.Parameters["View"].SetValue(camera.View); //(2000.0f, 2800.0f, 2000.0f)
            ScatterEffect.Parameters["WorldViewProjection"].SetValue(Matrix.CreateTranslation(camera.Position) * camera.View * camera.Projection);
            ScatterEffect.Parameters["LightPosition"].SetValue(new Vector3(2000.0f, 1000.0f, 2000.0f));


            
   

            DrawImageToRenderTarget(Input, renderTarget8, 0, 0, ScatterEffect, "Scatter");
            processBuffer = renderTarget8.GetTexture();
            colorBuffer = processBuffer;
            //base.Draw();




            ///////////////////////////////////////////////////////////
            //BLOOM EXTRACT
            //blurred bloom extract from color buffer
            //set shader settings   

            //GetInputFromFrameBuffer();

            bloomExtractEffect.Parameters["BloomThreshold"].SetValue(BloomThreshold);
            bloomExtractEffect.Parameters["xExtractTexture"].SetValue(processBuffer);
            DrawImageToRenderTarget(processBuffer, renderTarget5, 0, 0, bloomExtractEffect, "BloomExtract");

            bloomBuffer = renderTarget5.GetTexture();

            //blur bloom

           
            //6)set horizontal blur settings
            SetParametersBloom(GaussianBlurDirection.Horizontal); // Set horizontal parameters

            //7)render the colorbuffer into rendertarget2 using the blur shader from the effect file
            DrawImageToRenderTarget(bloomBuffer, renderTarget6, 0, 0, blurEffect, "Blur");

            //8)store the texture as the new blurbuffer
            bloomBuffer = renderTarget6.GetTexture();

            //9)set vertical blur settings
            SetParametersBloom(GaussianBlurDirection.Vertical); // Set horizontal parameters
            //10)render the horizontally blurred image into rendertarget3
            DrawImageToRenderTarget(bloomBuffer, renderTarget6, 0, 0, blurEffect, "Blur");

            //11)store the texture as the new blurbuffer
            bloomBuffer = renderTarget6.GetTexture();


            ///////////////////////////////////////////////////////////
            //BLOOM COMBINED
            //ShaderEnabled = true;
            //GetInputFromFrameBuffer();
            //colorBuffer = Input;
            //finally, blend the bloom buffer with our depth of field image            
            this.Effect = bloomCombineEffect;
            bloomCombineEffect.Parameters["BloomIntensity"].SetValue(BloomIntensity);
            bloomCombineEffect.Parameters["BaseIntensity"].SetValue(BaseIntensity);
            bloomCombineEffect.Parameters["BloomSaturation"].SetValue(BloomSaturation);
            bloomCombineEffect.Parameters["BaseSaturation"].SetValue(BaseSaturation);
            bloomCombineEffect.Parameters["xBloomTexture"].SetValue(bloomBuffer);
            bloomCombineEffect.Parameters["xBaseTexture"].SetValue(colorBuffer);

            //DrawWith(bloomBuffer);
            
                        DrawImageToRenderTarget(bloomBuffer, renderTarget8, 0, 0, bloomCombineEffect, "BloomCombine");
                        processBuffer = renderTarget8.GetTexture();
                     
                    
                        

                      //7) create depth map   
          
                       ///////////////////////////////////////////////////////////////////////////
                       //**DEPTHBUFFER
                       //

           
                       //2) set fog settings for depthmap specs       
                           Engine.GraphicsDevice.RenderState.FogEnable = true;
                           Engine.GraphicsDevice.RenderState.FogStart = dofStart;
                           Engine.GraphicsDevice.RenderState.FogDensity = dofDensity;
                           Engine.GraphicsDevice.RenderState.FogEnd = dofStart + dofLength;
                           Engine.GraphicsDevice.RenderState.FogTableMode = FogMode.Linear;
                           Engine.GraphicsDevice.RenderState.FogColor = Color.White;

                       //3)create a depthmap by drawing all components needed into rendertarget1
                           Engine.GraphicsDevice.SetRenderTarget(0, renderTarget1);            

             
               
                           //draw all models from our list that stored all last drawn models
                               foreach (Drawable3dComponent comp in this.Parent.DrawableRenderList)
                               {

                                   //draw the objects using the depthmap shader
                                   comp.DrawDepthMap();

                               } 

                       //4)reset the rendertarget for future rendering
                           Engine.GraphicsDevice.SetRenderTarget(0, null);

                       //5)store the print as our depthbuffer               
                           depthBuffer = renderTarget1.GetTexture();


                       //8) create blurbuffer from bloomed scene
            

                       /////////////////////////////////////////////////////////
                       //**BLURBUFFER HORIZONTAL
                       ////blurred colorbuffer for depth of field
                

                             //6)set horizontal blur settings
                                 SetParametersBlur(GaussianBlurDirection.Horizontal); // Set horizontal parameters

                             //7)render the colorbuffer into rendertarget2 using the blur shader from the effect file
                                 DrawImageToRenderTarget(processBuffer, renderTarget2, 0, 0, blurEffect, "Blur");

                             //8)store the texture as the new blurbuffer
                                 blurBuffer = renderTarget2.GetTexture();

                             //9)set vertical blur settings
                                 SetParametersBlur(GaussianBlurDirection.Vertical); // Set horizontal parameters
                             //10)render the horizontally blurred image into rendertarget3
                                 DrawImageToRenderTarget(blurBuffer, renderTarget2, 0, 0, blurEffect, "Blur");

                             //11)store the texture as the new blurbuffer
                                 blurBuffer = renderTarget2.GetTexture();

                                 ShaderEnabled = false;
                     
           
               
                             ////////////////////////////////////////////////////////////////////////
                             //DEPTH OF FIELD

            

                             ShaderEnabled = true;
                             DofEffect.CurrentTechnique = DofEffect.Techniques["DepthOfField"];
                             this.Effect = DofEffect;
                             Effect.Parameters["xColorTexture"].SetValue(processBuffer);
                             Effect.Parameters["xDepthTexture"].SetValue(depthBuffer);
                             Effect.Parameters["xBlurTexture"].SetValue(blurBuffer);

                  
                  
                         //for direct output: render the depthmap into our Input texture and draw it through base class
                             DrawWith(processBuffer);

                            
            //draw the particles on top of this one

                          
                            
            
           
            
        }


        //this function draws a requested texture into a determined rendertarget, using a requested effect
        private void DrawImageToRenderTarget(Texture2D texture, RenderTarget2D rendertarget, int offsetX, int offsetY, Effect effect, String technique)
        {
            Engine.GraphicsDevice.SetRenderTarget(0, rendertarget);
           
            effect.CurrentTechnique = effect.Techniques[technique];
            
            int width;
            int height;

            width = rendertarget.Width;
            height = rendertarget.Height;
            

            
                Engine.SpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
               
                effect.Begin();
                effect.CurrentTechnique.Passes[0].Begin();
                

                Engine.SpriteBatch.Draw(texture, new Rectangle(offsetX, offsetY, width, height), Color.White);
                Engine.SpriteBatch.End();
               
                effect.CurrentTechnique.Passes[0].End();
                effect.End();
                
            

            Engine.GraphicsDevice.SetRenderTarget(0, null);
        }
      
 
        
    }
 
    
}